#! /usr/bin/env bash
#
# Copyright (c) 2019 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

ovn_sim_builddir='@abs_builddir@'; export ovn_sim_builddir
ovn_sim_srcdir='@abs_top_srcdir@'; export ovn_sim_srcdir
ovs_sim_builddir='@OVSBUILDDIR@'

# Check that we've got proper builddir and srcdir.
if test ! -e "$ovs_sim_builddir"/utilities/ovs-sim; then
    echo "$ovs_sim_builddir/utilities/ovs-sim does not exist ($0 requires ovs-sim)" >&2
    exit 1
fi
if test ! -e "$ovn_sim_builddir"/northd/ovn-northd; then
    echo "$ovn_sim_builddir/northd/ovn-northd does not exist (need to run \"make\"?)" >&2
    exit 1
fi
if test ! -e "$ovn_sim_srcdir"/README.rst; then
    echo "$ovn_sim_srcdir/README.rst does not exist" >&2
    exit 1
fi

# Put built tools early in $PATH.
PATH=$ovn_sim_builddir/controller:$ovn_sim_builddir/northd:$ovn_sim_builddir/utilities:$PATH
export PATH

ovn-nbctl () { command ovn-nbctl -vsyslog:off "$@"; }; export -f ovn-nbctl
ovn-sbctl () { command ovn-sbctl -vsyslog:off "$@"; }; export -f ovn-sbctl

ovn_start_db() {
    local db=$1 model=$2 servers=$3 schema=$4
    local DB=$(echo $db | tr a-z A-Z)
    local schema_name=$(ovsdb-tool schema-name $schema)

    case $model in
        standalone | backup) ;;
        clustered)
            case $servers in
                [1-9] | [1-9][0-9]) ;;
                *) echo "${db}db servers must be between 1 and 99" >&2
                   exit 1
                   ;;
            esac
            ;;
        *)
            echo "unknown ${db}db model \"$model\"" >&2
            exit 1
            ;;
    esac

    ovn_start_ovsdb_server() {
        local i=$1; shift
        as ${db}$i ovsdb-server --detach --no-chdir --pidfile=$db.pid \
           -vsyslog:off -vconsole:off --log-file="$sim_base"/$db$i/$db.log \
           --remote=db:$schema_name,${DB}_Global,connections \
           --private-key=db:$schema_name,SSL,private_key \
           --certificate=db:$schema_name,SSL,certificate \
           --ca-cert=db:$schema_name,SSL,ca_cert \
           --ssl-protocols=db:$schema_name,SSL,ssl_protocols \
           --ssl-ciphers=db:$schema_name,SSL,ssl_ciphers \
           --unixctl=${db} --remote=punix:$db.ovsdb \
           "$sim_base"/$db$i/$db.db "$@"
    }

    ovn_prep_db() {
        local i=$1
        mkdir "$sim_base"/${db}$i
        touch "$sim_base"/${db}$i/.$db.db.~lock~
    }

    local n_remotes=1
    case $model in
        standalone)
            ovn_prep_db 1
            ovsdb-tool create "$sim_base"/${db}1/$db.db "$schema"
            ovn_start_ovsdb_server 1
            ;;
        backup)
            for i in 1 2; do
                ovn_prep_db $i
                ovsdb-tool create "$sim_base"/$db$i/$db.db "$schema"
            done
            ovn_start_ovsdb_server 1
            ovn_start_ovsdb_server 2 --sync-from=unix:"$sim_base"/${db}1/$db.ovsdb
            cat <<EOF
The backup server of OVN $DB can be accessed by:
* ovn-${db}ctl --db=unix:$sim_base/${db}2/$db.ovsdb
* ovs-appctl -t $sim_base/${db}2/${db}
The backup database file is $sim_base/${db}2/$db.db
EOF
            ;;
        clustered)
            n_remotes=$servers
            for i in $(seq $servers); do
                ovn_prep_db $i
                if test $i = 1; then
                    ovsdb-tool create-cluster "$sim_base"/$db$i/$db.db "$schema" unix:"$sim_base"/$db$i/db.raft
                else
                    ovsdb-tool join-cluster "$sim_base"/$db$i/$db.db $schema_name unix:"$sim_base"/$db$i/db.raft unix:"$sim_base"/${db}1/db.raft
                fi
                ovn_start_ovsdb_server $i
            done
            for i in $(seq $servers); do
                ovsdb-client wait unix:"$sim_base"/${db}$i/$db.ovsdb $schema_name connected
            done
            ;;
    esac

    remote=unix:"$sim_base"/${db}1/$db.ovsdb
    for i in `seq 2 $n_remotes`; do
        remote=$remote,unix:"$sim_base"/${db}$i/$db.ovsdb
    done
    eval OVN_${DB}_DB=\$remote
    eval export OVN_${DB}_DB
}
export -f ovn_start_db

ovn_start() {
    local nbdb_model=standalone
    local nbdb_servers=3
    local sbdb_model=standalone
    local sbdb_servers=3
    local prev=
    for option; do
        # This option-parsing mechanism borrowed from a Autoconf-generated
        # configure script under the following license:

        # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
        # 2002, 2003, 2004, 2005, 2006, 2009, 2013 Free Software Foundation, Inc.
        # This configure script is free software; the Free Software Foundation
        # gives unlimited permission to copy, distribute and modify it.

        # If the previous option needs an argument, assign it.
        if test -n "$prev"; then
            eval $prev=\$option
            prev=
            continue
        fi
        case $option in
            *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
            *) optarg=yes ;;
        esac

        case $dashdash$option in
            --)
                dashdash=yes ;;
            -h|--help)
                cat <<EOF
$FUNCNAME: start OVN central databases and daemons
usage: $FUNCNAME [OPTION...]

This creates and initializes the central OVN databases (northbound and
southbound), starts their ovsdb-server daemons, and starts the ovn-northd
daemon.

Options:
  --nbdb-model=standalone|backup|clustered    northbound database model
  --nbdb-servers=N     number of servers in nbdb cluster (default: 3)
  --sbdb-model=standalone|backup|clustered    southbound database model
  --sbdb-servers=N     number of servers in sbdb cluster (default: 3)
  -h, --help           Print this usage message.
EOF
                return
                ;;

            --nbdb-s*=*)
                nbdb_servers=$optarg
                nbdb_model=clustered
                ;;
            --nbdb-s*)
                prev=nbdb_servers
                nbdb_model=clustered
                ;;
            --nbdb-m*=*)
                nbdb_model=$optarg
                ;;
            --nbdb-m*)
                prev=nbdb_model
                ;;
            --sbdb-s*=*)
                sbdb_servers=$optarg
                sbdb_model=clustered
                ;;
            --sbdb-s*)
                prev=sbdb_servers
                sbdb_model=clustered
                ;;
            --sbdb-m*=*)
                sbdb_model=$optarg
                ;;
            --sbdb-m*)
                prev=sbdb_model
                ;;
            -*)
                echo "unrecognized option $option (use --help for help)" >&2
                return 1
                ;;
            *)
                echo "$option: non-option arguments not supported (use --help for help)" >&2
                return 1
                ;;
        esac
        shift
    done

    if test -d ovn-sb || test -d ovn-nb; then
        echo >&2 "OVN already started"
        return 1
    fi

    ovn_sim_setvars $sandbox

    # Build ovn man pages as part of ovn_start
    ovn_man_pages

    ovn_start_db nb "$nbdb_model" "$nbdb_servers" "$ovn_sim_srcdir"/ovn-nb.ovsschema
    ovn_start_db sb "$sbdb_model" "$sbdb_servers" "$ovn_sim_srcdir"/ovn-sb.ovsschema

    ovn-nbctl init
    ovn-sbctl init

    mkdir "$sim_base"/northd
    as northd ovn-northd --detach --no-chdir --pidfile=ovn-northd.pid -vconsole:off \
       --log-file=ovn-northd.log -vsyslog:off \
       --ovnnb-db="$OVN_NB_DB" --ovnsb-db="$OVN_SB_DB"
}
export -f ovn_start

ovn_attach() {
    if test "$1" = --help; then
        cat <<EOF
$FUNCNAME: attach default sandbox to an interconnection network for OVN
usage: $FUNCNAME NETWORK BRIDGE IP [MASKLEN]

This starts by doing everything that net_attach does.  Then it configures the
specified IP and MASKLEN (e.g. 192.168.0.1 and 24) on BRIDGE and starts
and configures ovn-controller.

MASKLEN defaults to 24 if it is not specified.
EOF
        return 0
    fi
    if test $# != 3 && test $# != 4; then
        echo >&2 "$FUNCNAME: wrong number of arguments (use --help for help)"
        return 1
    fi

    local net=$1 bridge=$2 ip=$3 masklen=${4-24}
    net_attach $net $bridge || return $?

    ovn_sim_setvars $sandbox

    ovs-appctl netdev-dummy/ip4addr $bridge $ip/$masklen >/dev/null
    ovs-appctl ovs/route/add $ip/$masklen $bridge > /dev/null
    ovs-vsctl \
        -- set Open_vSwitch . external-ids:system-id=$sandbox \
        -- set Open_vSwitch . external-ids:ovn-remote=$OVN_SB_DB \
        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
        -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip\
        -- add-br br-int \
        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
    ovn-controller --detach --no-chdir -vsyslog:off -vconsole:off \
                   --log-file=ovn-controller.${sandbox}.log \
                   --pidfile=ovn-controller.${sandbox}.pid
}
export -f ovn_attach

ovn_sim_setvars() {
    OVN_RUNDIR=$sim_base/$1; export OVN_RUNDIR
    OVN_LOGDIR=$sim_base/$1; export OVN_LOGDIR
    OVN_DBDIR=$sim_base/$1; export OVN_DBDIR
    OVN_SYSCONFDIR=$sim_base/$1; export OVN_SYSCONFDIR
}
export -f ovn_sim_setvars

ovn_as() {
    if test -n "$1"; then
        ovn_sim_setvars $1
    fi
    as $@
}
export -f ovn_as

ovn_man_pages() {
    # Easy access to OVN manpages.
    mkdir -p $sim_base/man
    mandir=`cd $sim_base/man && pwd`
    (cd "$ovn_sim_builddir" && ${MAKE-make} install-man install-man-rst mandir=$mandir EXTRA_RST_MANPAGES=ovn-sim.1.rst >/dev/null)
    # Note: MANPATH expected to be already exported by ovs-sim
    #MANPATH=$mandir:; export MANPATH
}
export -f ovn_man_pages

source ${ovs_sim_builddir}/utilities/ovs-sim
